/*
 * Copyright (c) 2009 Roman Savelyev
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

#include "drawingbuffer.h"
#include "conuisurface.h"
#include "conuisurfacelogger.h"

static const TText KClearText = ' ';

class TCommandSequencer
    {
public:
    TCommandSequencer(MDrawer& aDrawer);
    void SetBackground(ConUiDef::TColor aColor);
    void SetForeground(ConUiDef::TColor aColor);
    void SetBold(TBool aBold);
    void SetInverseVideo(TBool aInverseVideo);
    void SetPos(const TPoint& aPoint);
    void Write(TText aText);
    void FlushBuffer();
private:
    MDrawer& iDrawer;
    TBuf<32> iBuffer;
    };
///////////////////////////////////////////////////////////////////////////////
TCommandSequencer::TCommandSequencer(MDrawer& aDrawer) :
    iDrawer(aDrawer)
    {}

void TCommandSequencer::SetBackground(ConUiDef::TColor aColor)
    {
    if (iDrawer.Background() != aColor)
        {
        FlushBuffer();
        iDrawer.SetBackground(aColor);
        }
    }

void TCommandSequencer::SetForeground(ConUiDef::TColor aColor)
    {
    if (iDrawer.Foreground() != aColor)
        {
        FlushBuffer();
        iDrawer.SetForeground(aColor);
        }
    }

void TCommandSequencer::SetBold(TBool aBold)
    {
    TBool bold = iDrawer.Bold();
    if (!(bold&aBold) && bold|aBold)
        {
        FlushBuffer();
        iDrawer.SetBold(aBold);
        }
    }

void TCommandSequencer::SetInverseVideo(TBool aInverseVideo)
    {
    TBool inverseVideo = iDrawer.InverseVideo();
    if (!(inverseVideo&aInverseVideo) && inverseVideo|aInverseVideo)
        {
        FlushBuffer();
        iDrawer.SetInverseVideo(aInverseVideo);
        }
    }

void TCommandSequencer::SetPos(const TPoint& aPoint)
    {
    if (iDrawer.Pos() != aPoint)
        {
        FlushBuffer();
        iDrawer.SetPos(aPoint);
        }
    }

void TCommandSequencer::Write(TText aText)
    {
    if (iBuffer.Length() == iBuffer.MaxLength())
        {
        FlushBuffer();
        }
    iBuffer.Append(aText);
    }

void TCommandSequencer::FlushBuffer()
    {
    if (0 < iBuffer.Length())
        {
        iDrawer.Write(iBuffer);
        iBuffer.Zero();
        }
    }
///////////////////////////////////////////////////////////////////////////////
CDrawingBuffer* CDrawingBuffer::NewL(const TSize& aSize)
    {
    FL(_L("CDrawingBuffer::NewL"));
    TInt area = aSize.iWidth*aSize.iHeight;
    // background + foreground + bold + blinking + inversevideo
    TInt objSize = sizeof(CDrawingBuffer) + area*5*sizeof(TUint8);
    // text
    objSize += area*sizeof(TText);
    TUint8* bytes = new(ELeave) TUint8[objSize];
    return new(bytes) CDrawingBuffer(aSize);
    }

CDrawingBuffer::CDrawingBuffer(const TSize& aSize) :
    iRect(0, 0, aSize.iWidth, aSize.iHeight),
    iBlinkMode(EBlinkVisible)
    {
    FillBackground(iRect, ConUiDef::EDefault);
    FillForeground(iRect, ConUiDef::EDefault);
    FillBold(iRect, EFalse);
    FillBlinking(iRect, EFalse);
    FillInverseVideo(iRect, EFalse);
    ClearText(iRect);
    iArea = iRect.Width()*iRect.Height();
    }

void CDrawingBuffer::UpdateDrawerL(MDrawer& aDrawer, const RRegion& aRegion) const
    {
    FL(_L("CDrawingBuffer::UpdateTerminal"));
    RRegion clipped;
    clipped.Copy(aRegion);
    User::LeaveIfError(clipped.CheckError());
    CleanupClosePushL(clipped);
    clipped.ClipRect(Rect());
    TRect bounding = clipped.BoundingRect();
    TPoint pos = bounding.iTl;
    TCommandSequencer sequencer(aDrawer);
    for (; pos.iY<bounding.iBr.iY; pos.iY++)
        {
        for (; pos.iX<bounding.iBr.iX; pos.iX++)
            {
            if (aRegion.Contains(pos))
                {
                sequencer.SetPos(pos);
                sequencer.SetForeground(Background(pos));
                sequencer.SetBackground(Foreground(pos));
                sequencer.SetBold(Bold(pos));
                sequencer.SetInverseVideo(InverseVideo(pos));
                if (Blinking(pos) && EBlinkInvisible == BlinkMode())
                    {
                    sequencer.Write(KClearText);
                    }
                else
                    {
                    sequencer.Write(Text(pos));
                    }
                }
            }
        pos.iX = bounding.iTl.iX;
        }
    sequencer.FlushBuffer();
    CleanupStack::PopAndDestroy(&clipped);
    }

void CDrawingBuffer::FillBackground(const TRect& aRect, ConUiDef::TColor aColor)
    {
    TPoint pos = aRect.iTl;
    TInt length = aRect.Width();
    for (TPoint pos = aRect.iTl; pos.iY<aRect.iBr.iY; pos.iY++)
        {
        Mem::Fill(BackgroundArray(pos), length, (TUint8)aColor);
        }
    }

void CDrawingBuffer::FillForeground(const TRect& aRect, ConUiDef::TColor aColor)
    {
    TPoint pos = aRect.iTl;
    TInt length = aRect.Width();
    for (TPoint pos = aRect.iTl; pos.iY<aRect.iBr.iY; pos.iY++)
        {
        Mem::Fill(ForegroundArray(pos), length, (TUint8)aColor);
        }
    }

void CDrawingBuffer::FillBold(const TRect& aRect, TBool aBold)
    {
    TPoint pos = aRect.iTl;
    TInt length = aRect.Width();
    for (TPoint pos = aRect.iTl; pos.iY<aRect.iBr.iY; pos.iY++)
        {
        Mem::Fill(BoldArray(pos), length, (TUint8)aBold);
        }
    }

void CDrawingBuffer::FillBlinking(const TRect& aRect, TBool aBlinking)
    {
    TPoint pos = aRect.iTl;
    TInt length = aRect.Width();
    for (TPoint pos = aRect.iTl; pos.iY<aRect.iBr.iY; pos.iY++)
        {
        Mem::Fill(BlinkingArray(pos), length, (TUint8)aBlinking);
        }
    }

void CDrawingBuffer::FillInverseVideo(const TRect& aRect, TBool aInverseVideo)
    {
    TPoint pos = aRect.iTl;
    TInt length = aRect.Width();
    for (TPoint pos = aRect.iTl; pos.iY<aRect.iBr.iY; pos.iY++)
        {
        Mem::Fill(InverseVideoArray(pos), length, (TUint8)aInverseVideo);
        }
    }

void CDrawingBuffer::FillText(const TRect& aRect, TText aText)
    {
    TPoint pos = aRect.iTl;
    for (; pos.iY < aRect.iBr.iY; pos.iY++)
        {
        for (; pos.iX < aRect.iBr.iX; pos.iX++)
            {
            *TextArray(pos) = aText;
            }
        pos.iX = aRect.iTl.iX;
        }
    }

void CDrawingBuffer::FillText(const TPoint& aPos, const TDesC& aText)
    {
    TText* text = TextArray(aPos);
    Mem::Copy(text, aText.Ptr(), aText.Length());
    }

void CDrawingBuffer::ClearText(const TRect& aRect)
    {
    FillText(aRect, KClearText);
    }

ConUiDef::TColor CDrawingBuffer::Background(const TPoint& aPos) const
    {
    return (ConUiDef::TColor)*BackgroundArray(aPos);
    }

ConUiDef::TColor CDrawingBuffer::Foreground(const TPoint& aPos) const
    {
    return (ConUiDef::TColor)*ForegroundArray(aPos);
    }

TBool CDrawingBuffer::Bold(const TPoint& aPos) const
    {
    return *BoldArray(aPos);
    }

TBool CDrawingBuffer::Blinking(const TPoint& aPos) const
    {
    return *BlinkingArray(aPos);
    }

TBool CDrawingBuffer::InverseVideo(const TPoint& aPos) const
    {
    return *InverseVideoArray(aPos);
    }

TText CDrawingBuffer::Text(const TPoint& aPos) const
    {
    return *TextArray(aPos);
    }

TPtrC CDrawingBuffer::Text(const TPoint& aPos, TInt aLength) const
    {
    return TPtrC(TextArray(aPos), aLength);
    }

TUint8* CDrawingBuffer::ForegroundArray(const TPoint& aPos)
    {
    TUint8* foregroundArray = (TUint8*)this + sizeof(CDrawingBuffer) + iArea*sizeof(TText);
    return foregroundArray + Offset(aPos);
    }

const TUint8* CDrawingBuffer::ForegroundArray(const TPoint& aPos) const
    {
    // CDrawingBuffer + text
    const TUint8* foregroundArray = (const TUint8*)this + sizeof(CDrawingBuffer) + iArea*sizeof(TText);
    return foregroundArray + Offset(aPos);
    }

TUint8* CDrawingBuffer::BackgroundArray(const TPoint& aPos)
    {
    // CDrawingBuffer + text + foreground
    TUint8* backgroundArray = (TUint8*)this + sizeof(CDrawingBuffer) + iArea*sizeof(TText) + iArea;
    return backgroundArray + Offset(aPos);
    }

const TUint8* CDrawingBuffer::BackgroundArray(const TPoint& aPos) const
    {
    // CDrawingBuffer + text + foreground
    const TUint8* backgroundArray = (const TUint8*)this + sizeof(CDrawingBuffer) + iArea*sizeof(TText) + iArea;
    return backgroundArray + Offset(aPos);
    }

TUint8* CDrawingBuffer::BoldArray(const TPoint& aPos)
    {
    // CDrawingBuffer + text + foreground + background
    TUint8* boldArray = (TUint8*)this + sizeof(CDrawingBuffer) + iArea*sizeof(TText) + iArea*2;
    return boldArray + Offset(aPos);
    }

const TUint8* CDrawingBuffer::BoldArray(const TPoint& aPos) const
    {
    // CDrawingBuffer + text + foreground + background
    const TUint8* boldArray = (const TUint8*)this + sizeof(CDrawingBuffer) + iArea*sizeof(TText) + iArea*2;
    return boldArray + Offset(aPos);
    }

TUint8* CDrawingBuffer::BlinkingArray(const TPoint& aPos)
    {
    // CDrawingBuffer + text + foreground + background + bold
    TUint8* boldArray = (TUint8*)this + sizeof(CDrawingBuffer) + iArea*sizeof(TText) + iArea*3;
    return boldArray + Offset(aPos);
    }

const TUint8* CDrawingBuffer::BlinkingArray(const TPoint& aPos) const
    {
    // CDrawingBuffer + text + foreground + background + bold
    const TUint8* boldArray = (const TUint8*)this + sizeof(CDrawingBuffer) + iArea*sizeof(TText) + iArea*3;
    return boldArray + Offset(aPos);
    }

TUint8* CDrawingBuffer::InverseVideoArray(const TPoint& aPos)
    {
    // CDrawingBuffer + text + foreground + background + bold + blinking
    TUint8* inverseArray = (TUint8*)this + sizeof(CDrawingBuffer) + iArea*sizeof(TText) + iArea*4;
    return inverseArray + Offset(aPos);
    }

const TUint8* CDrawingBuffer::InverseVideoArray(const TPoint& aPos) const
    {
    // CDrawingBuffer + text + foreground + background + bold + blinking
    const TUint8* inverseArray = (const TUint8*)this + sizeof(CDrawingBuffer) + iArea*sizeof(TText) + iArea*4;
    return inverseArray + Offset(aPos);
    }

TText* CDrawingBuffer::TextArray(const TPoint& aPos)
    {
    // CDrawingBuffer
    TText* textArray = (TText*)this + sizeof(CDrawingBuffer);
    return textArray + Offset(aPos);
    }

const TText* CDrawingBuffer::TextArray(const TPoint& aPos) const
    {
    // CDrawingBuffer
    const TText* textArray = (const TText*)this + sizeof(CDrawingBuffer);
    return textArray + Offset(aPos);
    }
